
#include <BFont>
#include <BPainter>
#include "member_BTextEdit.h"

using namespace BWE;

member_BTextEdit::member_BTextEdit(BTextEdit* textEdit)
{
	boss = textEdit;
	lineSpacing = 0;
	wordWrap = true;
	lineFeed = false;
	lineHeight = 0;
	lineStyle = 0;
	readOnly = false;
	blinkTick = 0;
	blinkTime = 1;
	blinkShown = false;

	dirty = false;
	editing = false;
	changed = false;
	pressed = false;
	lineSelecting = false;
	grabSelction = false;
	gotoInput = false;
	pressIndex = 0;
	hoverIndex = 0;

	ILineBlock& block = blocklst.append();
	block.font = boss->realFont();
	block.dirty = true;

	inputBlock = blocklst.end();
	hoverBlock = blocklst.end();
	firstBlock = blocklst.end();
	lastBlock = blocklst.end();

	stackSize = -1;
	operaIndex = 0;
}
member_BTextEdit::~member_BTextEdit()
{

}

void member_BTextEdit::freshText()
{
	if (changed)
	{
		text.reset();
		for (auto it = blocklst.begin(); it.valid(); ++it)
		{
			ILineBlock& block = *it;
			text.append(block.text);
		}
		changed = false;
	}
}
void member_BTextEdit::emitEdited(BReal time)
{
	dirty = true;
	editing = true;
	changed = true;
	gotoInput = true;
	if (boss->checks(Signal_TextChanged))
	{
		freshText();
		boss->emit(Signal_TextChanged, text);
	}
	if (boss->checks(Signal_TextEdited))
	{
		freshText();
		boss->emit(Signal_TextEdited, text);
	}
	if (boss->checks(Signal_LineEdited))
	{
		boss->emit(Signal_LineEdited, inputBlock.index());
	}
	freshFormat();
	blinkShown = true;
	blinkTick = time + (BReal)0.5;
	boss->fresh();
}
bool member_BTextEdit::freshFormat()
{
	bool isdirty = false;
	if (textFormat)
	{
		freshText();
		textFormat->changed(text);
		isdirty = true;
	}
	for (auto it = blocklst.begin(); it.valid(); ++it)
	{
		ILineBlock& block = *it;
		if (BTextFormat* format = block.format)
		{
			format->changed(block.text);
			block.dirty = true;
			isdirty = true;
		}
	}
	if (isdirty)
	{
		dirty = true;
		boss->fresh();
		return true;
	}
	return false;
}

void member_BTextEdit::resetBlocks()
{
	const BFont* font = boss->realFont();
	blocklst.clear();
	if (text.empty())
	{
		ILineBlock& block = blocklst.append();
		block.lineHeight = lineHeight;
		block.lineSpacing = lineSpacing;
		block.font = font;
		block.dirty = true;
	}
	else
	{
		int pos = 0;
		BString line;
		while (pos < text.size())
		{
			ILineBlock& block = blocklst.append();
			pos = text.line(line, pos);
			block.reset(line);
			block.lineHeight = lineHeight;
			block.lineSpacing = lineSpacing;
			block.font = font;
			block.dirty = true;
		}
	}

	inputBlock.reset();
	hoverBlock.reset();
	hoverIndex = -1;
	pressBlock.reset();
	pressIndex = -1;

	firstBlock.reset();
	lastBlock.reset();
	showBegin.reset();
	showEnd.reset();
}
void member_BTextEdit::appendBlocks(const BString& str)
{
	const BFont* font = boss->realFont();

	int pos = text.size();
	text.append(str);

	BString line;
	if (blocklst.size() && blocklst.last().cells.last() != '\n')
	{
		ILineBlock& block = blocklst.last();
		pos = text.line(line, pos);
		block.reset(line);
		block.lineHeight = lineHeight;
		block.lineSpacing = lineSpacing;
		block.font = font;
		block.dirty = true;
	}

	while (pos < text.size())
	{
		ILineBlock& block = blocklst.append();
		pos = text.line(line, pos);
		block.reset(line);
		block.lineHeight = lineHeight;
		block.lineSpacing = lineSpacing;
		block.font = font;
		block.dirty = true;
	}

	ILineBlock& last = blocklst.last();
	if (last.text.endWith('\n'))
	{
		ILineBlock& block = blocklst.append();
		block.lineHeight = lineHeight;
		block.lineSpacing = lineSpacing;
		block.font = font;
		block.dirty = true;
	}
}
void member_BTextEdit::freshBlocks()
{
	const BRect& crect = boss->centerRect();
	BRect area = boss->area();
	int x = area.x();
	int y = area.y();
	int width = 0;
	int height = 0;
	int pos = 0;
	for (auto it = blocklst.begin(); it.valid(); it++)
	{
		ILineBlock& block = *it;
		if (block.dirty)
		{
			if (lineFeed)
				block.adjust(crect.width());
			else
				block.adjust(0);
		}
		if (block.visible)
		{
			block.rect.setPos(x, y);
			y += block.rect.height() + lineSpacing;
			height += block.rect.height() + lineSpacing;
			if (width < block.textSize.width())
				width = block.textSize.width();
		}
		block.range.min() = pos;
		pos = pos + block.cells.size();
		block.range.max() = pos - 1;
	}

	area.setWidth(width);
	area.setHeight(height);
	boss->setArea(area);

	BSize areaSize = boss->areaSize();
	for (auto it = blocklst.begin(); it.valid(); it++)
	{
		ILineBlock& block = *it;
		block.rect.setWidth(areaSize.width());
	}

	freshVisualRange();
}
void member_BTextEdit::freshVisualRange()
{
	BRect crect = boss->centerRect();
	showBegin = blocklst.begin();
	showEnd = blocklst.end();
	while (showBegin.valid())
	{
		if (!showBegin->visible)
		{
			showBegin++;
			continue;
		}
		if (showBegin->rect.bottom() > crect.top())
		{
			showEnd = showBegin;
			while (showEnd.valid())
			{
				if (!showEnd->visible)
				{
					showEnd++;
					continue;
				}
				if (showEnd->rect.top() > crect.bottom())
					break;
				showEnd++;
			}
			break;
		}
		showBegin++;
	}
}
BLine2 member_BTextEdit::inputLine()
{
	BLine2 line;
	if (inputBlock.valid())
	{
		if (inputBlock->cells.check(inputBlock->index))
		{
			ICodeCell& cell = inputBlock->cells[inputBlock->index];
			BRect rect = cell.rect + inputBlock->rect.pos();
			line.set(rect.leftTop(), rect.leftBottom());
		}
		else if (inputBlock->index == 0 || inputBlock->cells.empty())
		{
			BRect rect = inputBlock->rect;
			line.set(rect.leftTop(), rect.leftBottom());
		}
		else if (inputBlock->index == inputBlock->codeCount())
		{
			ICodeCell& cell = inputBlock->cells.last();
			BRect rect = cell.rect + inputBlock->rect.pos();
			line.set(rect.rightTop(), rect.rightBottom());
		}
	}
	if (blinkShown)
	{
		boss->fresh();
	}
	return line;
}
void member_BTextEdit::wrapInputLine()
{
	BLine2 line = inputLine();
	BRect rect(line.begin());
	rect.expand(line.end());
	boss->scrollToRect(rect, Align_None);
}

BOpera* member_BTextEdit::pushOpera(BOpera::Mode mode, BReal time)
{
	if (stackSize == 0)
		return 0;
	if (operaIndex < operaStack.size())
	{
		operaStack.remove(operaIndex, operaStack.size());
	}
	BOpera* opera = 0;
	if (stackSize > 0 && operaStack.size() == stackSize)
	{
		BOperaHolder opera = operaStack.first();
		operaStack.remove(0, 1);
		operaStack.push(opera);
		opera->mode = mode;
		opera->time = time;
	}
	else
	{
		opera = new BOpera(mode, time);
		operaStack.push(opera);
		operaIndex++;
	}
	return opera;
}
void member_BTextEdit::operaInput(BReal time, const BCode& code)
{
	if (stackSize == 0)
		return;
	BOpera* last = operaStack.last();
	if (last && last->mode == BOpera::Mode_Input && time - last->time < 2)
	{
		last->time = time;
		last->text << code;
		boss->emit(Signal_Opera, last);
		return;
	}
	if (BOpera* opera = pushOpera(BOpera::Mode_Input, time))
	{
		opera->line = inputBlock.index();
		opera->index = inputBlock->index;
		opera->text << code;
		boss->emit(Signal_Opera, opera);
	}
}
void member_BTextEdit::operaInput(BReal time, const BString& text)
{
	if (stackSize == 0)
		return;
	BOpera* last = operaStack.last();
	if (last && last->mode == BOpera::Mode_Input && time - last->time < 2)
	{
		last->time = time;
		last->text << text;
		boss->emit(Signal_Opera, last);
		return;
	}
	if (BOpera* opera = pushOpera(BOpera::Mode_Input, time))
	{
		opera->line = inputBlock.index();
		opera->index = inputBlock->index;
		opera->text << text;
		boss->emit(Signal_Opera, opera);
	}
}
void member_BTextEdit::operaInsert(BReal time, const BString& text)
{
	if (BOpera* opera = pushOpera(BOpera::Mode_Insert, time))
	{
		opera->line = inputBlock.index();
		opera->index = inputBlock->index;
		opera->text << text;
		boss->emit(Signal_Opera, opera);
	}
}
void member_BTextEdit::operaDelete(BReal time)
{
	if (BOpera* opera = pushOpera(BOpera::Mode_Delete, time))
	{
		opera->line = inputBlock.index();
		opera->index = inputBlock->index;
		selectedText(opera->text);
		boss->emit(Signal_Opera, opera);
	}
}
void member_BTextEdit::operaRemove(BReal time)
{
	if (BOpera* opera = pushOpera(BOpera::Mode_Remove, time))
	{
		opera->line = inputBlock.index();
		opera->index = inputBlock->index;
		selectedText(opera->text);
		boss->emit(Signal_Opera, opera);
	}
}

bool member_BTextEdit::seekPrevWord()
{
	if (inputBlock->index == 0)
	{
		if (inputBlock.index() == 0)
			return false;
		inputBlock--;
		inputBlock->index = inputBlock->tail();
		inputBlock->count = 0;
	}
	if (inputBlock->seekPrevWord() < 0)
	{
		if (inputBlock.index() == 0)
			return false;
		inputBlock--;
		inputBlock->index = inputBlock->tail();
		inputBlock->count = 0;
	}
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekNextWord()
{
	if (inputBlock->cells(inputBlock->index).code == '\n')
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		inputBlock->index = 0;
		inputBlock->count = 0;
	}
	if (inputBlock->seekNextWord() >= inputBlock->cells.size())
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		inputBlock->index = 0;
		inputBlock->count = 0;
	}
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekPrev()
{
	if (inputBlock->seekPrevCode() < 0)
	{
		if (inputBlock.index() == 0)
			return false;
		inputBlock--;
		inputBlock->index = inputBlock->tail();
		inputBlock->count = 0;
	}
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekNext()
{
	if (inputBlock->seekNextCode() >= inputBlock->cells.size())
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		inputBlock->index = 0;
		inputBlock->count = 0;
	}
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekUp()
{
	BPoint point = inputLine().center();
	point.y() -= lineHeight + lineSpacing;
	int index = inputBlock->indexAt(point);
	if (index >= 0)
	{
		if (inputBlock->index == index)
			return false;
		inputBlock->index = index;
	}
	else
	{
		if (inputBlock.index() <= 0)
			return false;
		inputBlock--;
		int inputIndex = inputBlock->indexAt(point);
		inputBlock->index = bClamp(inputIndex, 0, inputBlock->tail());
	}
	inputBlock->count = 0;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekDown()
{
	BPoint point = inputLine().center();
	point.y() += lineHeight + lineSpacing;
	int index = inputBlock->indexAt(point);
	if (inputBlock->cells.check(index))
	{
		if (inputBlock->index == index)
			return false;
		inputBlock->index = index;
	}
	else
	{
		if (inputBlock.index() == blocklst.size() - 1)
			return false;
		inputBlock++;
		int inputIndex = inputBlock->indexAt(point);
		inputBlock->index = bClamp(inputIndex, 0, inputBlock->tail());
	}
	inputBlock->count = 0;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekHome()
{
	int home = inputBlock->home();
	if (inputBlock->index == home)
	{
		inputBlock->index = 0;
	}
	else
	{
		inputBlock->index = home;
	}
	inputBlock->count = 0;
	boss->fresh();
	return true;
}
bool member_BTextEdit::seekEnd()
{
	int end = inputBlock->end();
	if (inputBlock->index == end)
	{
		inputBlock->index = inputBlock->tail();
	}
	else
	{
		inputBlock->index = end;
	}
	inputBlock->count = 0;
	boss->fresh();
	return true;
}

bool member_BTextEdit::insertCode(const BCode& code)
{
	if (code.num() == 0)
		return false;
	if (code == '\n')
	{
		inputBlock->insert(code);

		BString text;
		inputBlock->collect(text, inputBlock->index, inputBlock->cells.size() - inputBlock->index);
		inputBlock->remove(inputBlock->index, inputBlock->cells.size() - inputBlock->index);
		inputBlock->index = 0;
		inputBlock->count = 0;

		blocklst.insert(inputBlock + 1);
		ILineBlockIterator next = inputBlock + 1;
		next->reset(text);
		next->font = boss->realFont();
		next->dirty = true;

		inputBlock = next;
	}
	else
	{
		inputBlock->insert(code);
	}
	return true;
}
bool member_BTextEdit::insertText(const BString& text)
{
	if (text.empty())
		return false;
	if (text.contain('\n'))
	{
		int pos = 0;
		BString line;
		while (pos < text.size())
		{
			pos = text.line(line, pos);
			if (line.endWith('\n'))
			{
				blocklst.insert(inputBlock);
				auto prevBlock = inputBlock - 1;
				if (inputBlock->index > 0)
				{
					BString prevStr;
					inputBlock->collect(prevStr, 0, inputBlock->index);
					inputBlock->remove(0, inputBlock->index);
					prevBlock->text = prevStr;
				}
				prevBlock->reset();
				prevBlock->seekTail();
				prevBlock->font = boss->realFont();
				prevBlock->dirty = true;
				prevBlock->insert(line);
			}
			else
			{
				inputBlock->insert(line);
			}
		}
		inputBlock.correct();
		hoverBlock.correct();
		firstBlock.correct();
		lastBlock.correct();
		showBegin.correct();
		showEnd.correct();
	}
	else
	{
		inputBlock->insert(text);
	}
	return true;
}

bool member_BTextEdit::selectBlocks(int line, int index, int count)
{
	ILineBlockIterator headBlock = blocklst.seek(line);
	int headIndex = index;
	ILineBlockIterator tailBlock = headBlock;
	int tailIndex = index + count;
	count -= headBlock->codeCount() - index;
	if (count > 0)
	{
		for (auto it = headBlock.next(); it.valid(); it++)
		{
			if (it->codeCount() > count)
			{
				lastBlock = it;
				tailIndex = count;
				break;
			}
		}
	}
	if (headBlock.empty() || tailBlock.empty())
		return false;
	if (headBlock == tailBlock)
	{
		inputBlock = headBlock;
		inputBlock->index = headIndex;
		inputBlock->count = tailIndex - headIndex;
		this->firstBlock.reset();
		this->lastBlock.reset();
		pressBlock = headBlock;
		pressIndex = headIndex;
	}
	else
	{
		this->firstBlock = headBlock;
		this->firstBlock->index = headIndex;
		this->firstBlock->count = headBlock->cells.size() - headIndex;
		this->lastBlock = tailBlock;
		this->lastBlock->index = 0;
		this->lastBlock->count = tailIndex;
	}
	return true;
}
bool member_BTextEdit::selectBlocks(int min, int max)
{
	ILineBlockIterator firstBlock;
	int firstIndex = -1;
	for (auto it = blocklst.begin(); it.valid(); it++)
	{
		if (it->range.contain(min))
		{
			firstBlock = it;
			firstIndex = min - it->range.min();
			break;
		}
	}
	ILineBlockIterator lastBlock;
	int lastIndex = -1;
	for (auto it = firstBlock; it.valid(); it++)
	{
		if (it->range.contain(max))
		{
			lastBlock = it;
			lastIndex = max - it->range.min();
			break;
		}
	}
	if (firstBlock.empty() || lastBlock.empty())
		return false;
	if (firstBlock == lastBlock)
	{
		inputBlock = firstBlock;
		inputBlock->index = firstIndex;
		inputBlock->count = lastIndex - firstIndex;
		firstBlock.reset();
		lastBlock.reset();
		pressBlock = firstBlock;
		pressIndex = firstIndex;
	}
	else
	{
		firstBlock = firstBlock;
		firstBlock->index = firstIndex;
		firstBlock->count = firstBlock->cells.size() - firstIndex;
		lastBlock = lastBlock;
		lastBlock->index = 0;
		lastBlock->count = lastIndex;
	}
	return true;
}
bool member_BTextEdit::selectBlocks()
{
	if (pressBlock.empty() || hoverBlock.empty())
		return false;
	if (pressBlock == hoverBlock)
	{
		inputBlock = pressBlock;
		inputBlock->count = abs(pressIndex - hoverIndex);
		inputBlock->index = bMin(pressIndex, hoverIndex);
	}
	else
	{
		if (pressBlock < hoverBlock)
		{
			firstBlock = pressBlock;
			firstBlock->index = pressIndex;
			firstBlock->count = firstBlock->cells.size() - pressIndex;
			lastBlock = hoverBlock;
			lastBlock->index = 0;
			lastBlock->count = hoverIndex;
		}
		else
		{
			firstBlock = hoverBlock;
			firstBlock->index = hoverIndex;
			firstBlock->count = firstBlock->cells.size() - hoverIndex;
			lastBlock = pressBlock;
			lastBlock->index = 0;
			lastBlock->count = pressIndex;
		}
		for (auto it = firstBlock + 1; it < lastBlock; it++)
		{
			it->index = 0;
			it->count = it->cells.size();
		}
	}
	return true;
}
bool member_BTextEdit::selectLines()
{
	if (pressBlock.empty() || hoverBlock.empty())
		return false;
	if (pressBlock == hoverBlock)
	{
		inputBlock = pressBlock;
		inputBlock->count = abs(pressIndex - hoverIndex);
		inputBlock->index = bMin(pressIndex, hoverIndex);
		if (hoverBlock.next().valid())
			inputBlock = hoverBlock.next();
		else
			inputBlock = hoverBlock;
	}
	else
	{
		if (pressBlock < hoverBlock)
		{
			firstBlock = pressBlock;
			firstBlock->index = 0;
			firstBlock->count = firstBlock->cells.size();
			lastBlock = hoverBlock;
			lastBlock->index = 0;
			lastBlock->count = lastBlock->cells.size();
			if (hoverBlock.next().valid())
				inputBlock = hoverBlock.next();
			else
				inputBlock = hoverBlock;
		}
		else
		{
			firstBlock = hoverBlock;
			firstBlock->index = 0;
			firstBlock->count = firstBlock->cells.size();
			lastBlock = pressBlock;
			lastBlock->index = 0;
			lastBlock->count = lastBlock->cells.size();
			inputBlock = hoverBlock;
		}
		for (auto it = firstBlock + 1; it < lastBlock; it++)
		{
			it->index = 0;
			it->count = it->cells.size();
		}
	}
	return true;
}
void member_BTextEdit::unselect()
{
	if (inputBlock.valid())
		inputBlock->count = 0;
	if (firstBlock.valid())
	{
		firstBlock->count = 0;
		firstBlock.reset();
	}
	if (lastBlock.valid())
	{
		lastBlock->count = 0;
		lastBlock.reset();
	}
}

bool member_BTextEdit::removeSelection()
{
	if (firstBlock.valid())
	{
		for (auto it = firstBlock; it <= lastBlock; it++)
		{
			it->removeSelection();
		}
		auto nextBlock = firstBlock + 1;
		blocklst.erase(nextBlock, lastBlock);
		if (firstBlock->cells.last() != '\n')
		{
			firstBlock->append(lastBlock->text);
			blocklst.erase(lastBlock);
		}
		inputBlock = firstBlock;
		hoverBlock.reset();
		firstBlock.reset();
		lastBlock.reset();
		showBegin.reset();
		showEnd.reset();
		return true;
	}
	if (inputBlock.valid() && inputBlock->count)
	{
		inputBlock->removeSelection();
		if (inputBlock->cells.last() != '\n')
		{
			ILineBlockIterator nextBlock = inputBlock + 1;
			if (nextBlock.valid())
			{
				inputBlock->append(nextBlock->text);
				blocklst.erase(nextBlock);
			}
		}
		return true;
	}
	return false;
}

bool member_BTextEdit::hasSelection() const
{
	if (firstBlock.valid() && lastBlock.valid())
	{
		return true;
	}
	else if (inputBlock.valid() && inputBlock->count > 0)
	{
		return true;
	}
	return false;
}
void member_BTextEdit::selectedRange(BRange& range) const
{
	if (firstBlock.valid() && lastBlock.valid())
	{
		range.min() = firstBlock->range.min() + firstBlock->index;
		range.max() = lastBlock->range.min() + lastBlock->index + lastBlock->count;
	}
	else if (inputBlock.valid())
	{
		range.min() = inputBlock->range.min() + inputBlock->index;
		range.max() = inputBlock->range.min() + inputBlock->index + inputBlock->count;
	}
}
void member_BTextEdit::member_BTextEdit::selectedText(BString& text) const
{
	text.reset();
	if (firstBlock.valid() && lastBlock.valid())
	{
		for (auto it = firstBlock; it <= lastBlock; it++)
		{
			it->collect(text, it->index, it->count);
			it->removeSelection();
		}
	}
	else if (inputBlock.valid())
	{
		inputBlock->collect(text, inputBlock->index, inputBlock->count);
	}
}

void member_BTextEdit::slotTextFormatDirty(BObject* object, const BValue& value)
{
	dirty = true;
	boss->fresh();
}
void member_BTextEdit::slotLineFormatDirty(BObject* object, const BValue& value)
{
	dirty = true;
	boss->fresh();
}
